/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package co.infinum.goldfinger.crypto;

import co.infinum.goldfinger.Goldfinger;
import co.infinum.goldfinger.MissingHardwareException;
import co.infinum.goldfinger.FaceAuthResult;
import ohos.aafwk.ability.Ability;
import ohos.biometrics.authentication.BiometricAuthentication;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * A reprint module that authenticates fingerprint using the marshmallow Imprint API.
 * <p/>
 * This module supports most phones running  Marshmallow.
 * <p/>
 * The values of error codes provided by the api overlap for fatal and non-fatal authentication
 * failures. Fatal error code constants start with FINGERPRINT_ERROR, and non-fatal error codes
 * start with FINGERPRINT_ACQUIRED.
 */
public class MarshmallowReprintModule implements ReprintModule {
    public static final int TAG = 1;
    private final Ability context;
    private Goldfinger.Logger logger;
    private BiometricAuthentication.SecureLevel level;
    private BiometricAuthentication.AuthType type;
    private boolean isLocalAuth;
    private BiometricAuthentication mBiometricAuthentication;
    private Goldfinger.Callback listener;
    private Goldfinger.RestartPredicate restartPredicate;
    private int reStartCount = 5;
    private String key;
    private String value;
    private static final int POOL_CORE_SIZE = 2;
    private static final int POOL_MAX_SIZE = 5;
    private static final int NO_FACE_RET = -1;
    private static final int KEEP_ALIVE_TIME = 3;
    private static final int QUEUE_SIZE = 6;
    private static final int RET_NOT_SUPPORTED = 1;
    private static final int RET_SAFE_LEVEL_NOT_SUPPORTED = 2;
    private static final int RET_NOT_LOCAL = 3;

    public MarshmallowReprintModule(Ability context, Goldfinger.Logger logger) {
        this.context = context;
        this.logger = logger;
    }

    private BiometricAuthentication getBiometricAuthentication() {
        try {
            return BiometricAuthentication.getInstance(context);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public int tag() {
        return TAG;
    }

    @Override
    public void init(BiometricAuthentication.AuthType type, BiometricAuthentication.SecureLevel level) {
        this.level = level;
        this.type = type;
    }

    /**
     * 检验设备是否有人脸识别功能
     * BiometricAuthentication.AuthType中有三个类别
     * 分别为AUTH_TYPE_BIOMETRIC_FINGERPRINT_ONLY指纹识别
     * AUTH_TYPE_BIOMETRIC_FACE_ONLY脸部识别
     * AUTH_TYPE_BIOMETRIC_ALL指纹和面部
     * BiometricAuthentication.SecureLevel 2D人脸识别建议使用SECURE_LEVEL_S2，3D人脸识别建议使用SECURE_LEVEL_S3
     *
     * @return retChkAuthAvb 0是支持，1是不支持，2安全级别不支持 3不是本地认证 4无人脸录入
     */
    @Override
    public int isHardwarePresent() {
        try {
            mBiometricAuthentication = getBiometricAuthentication();
            if (mBiometricAuthentication == null) {
                return FaceAuthResult.ERROR_UNKNOWN;
            }
            int retChkAuthAvb = mBiometricAuthentication.checkAuthenticationAvailability(
                    type, level, true);
            return retChkAuthAvb;
        } catch (Exception e) {
            logger.logException(e, "MarshmallowReprintModule: isHardwareDetected failed unexpectedly");
            return FaceAuthResult.ERROR_UNKNOWN;
        }
    }

    @Override
    public void authenticate(final BiometricAuthentication mBiometricAuthentication,
           final Goldfinger.Callback listener,
           final Goldfinger.RestartPredicate restartPredicate) {
        this.mBiometricAuthentication = mBiometricAuthentication;
        this.listener = listener;
        this.restartPredicate = restartPredicate;
        authenticate(mBiometricAuthentication, listener, restartPredicate, 5, null, null);
    }

    @Override
    public void authenticate(BiometricAuthentication mBiometricAuthentication, Goldfinger.Callback listener, Goldfinger.RestartPredicate restartPredicate, String key, String value) {
        this.mBiometricAuthentication = mBiometricAuthentication;
        this.listener = listener;
        this.restartPredicate = restartPredicate;
        this.key = key;
        this.value = value;
        authenticate(mBiometricAuthentication, listener, restartPredicate, 5, key, value);
    }


    /**
     * 取消 人脸识别
     *
     * @return 成功
     */
    @Override
    public int cancelAuthentication() {
        mBiometricAuthentication = getBiometricAuthentication();
        if (mBiometricAuthentication == null) {
            return FaceAuthResult.ERROR_UNKNOWN;
        }
        return mBiometricAuthentication.cancelAuthenticationAction();
    }

    void authenticate(final BiometricAuthentication mBiometricAuthentication,
         final Goldfinger.Callback listener,
         final Goldfinger.RestartPredicate restartPredicate,
         int restartCount,
         String key,
         String value) throws SecurityException {
        this.mBiometricAuthentication = mBiometricAuthentication;
        this.listener = listener;
        this.restartPredicate = restartPredicate;
        this.reStartCount = restartCount;
        this.key = key;
        this.value = value;
        if (mBiometricAuthentication == null) {
            listener.onError(new MissingHardwareException(FaceAuthResult.getString(context, ResourceTable.String_ERROR_UNKNOWN)));
            return;
        }
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                POOL_CORE_SIZE, POOL_MAX_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(QUEUE_SIZE), new ThreadPoolExecutor.DiscardOldestPolicy());
        pool.submit(runnable);
    }


    /**
     * 新建线程进行认证，避免阻塞其他任务
     */
    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            // 开始认证
            int retExcAuth = mBiometricAuthentication.execAuthenticationAction(
                    type,
                    level,
                    isLocalAuth,
                    false,
                    null);
//                LogUtil.LogE("retExcAuth:"+retExcAuth);
            //获得认证过程中的提示信息
            BiometricAuthentication.AuthenticationTips mTips = mBiometricAuthentication.getAuthenticationTips();
            if (listener == null) {
                listener.onResult(new Goldfinger.Result(Goldfinger.Type.ERROR, FaceAuthResult.ERROR_UNKNOWN, value, "AuthenticationListener is null", mTips.tipValue, TAG));
                return;
            }
            if (mTips.tipValue == 0) {//成功回调
                listener.onResult(new Goldfinger.Result(Goldfinger.Type.SUCCESS, FaceAuthResult.AUTH_SUCCESS, value, "认证成功", mTips.errorCode, TAG));
            } else if (mTips.tipValue == 1) {//验证失败  不是本人
                listener.onResult(new Goldfinger.Result(Goldfinger.Type.ERROR, FaceAuthResult.AUTH_FAIL, value, mTips.tipInfo, mTips.errorCode, TAG));
            } else if (mTips.tipValue == 3) {//超时
                int i = reStartCount + 1;
                if (i <= reStartCount) {
//                        LogUtil.LogE("超时回调"+i);
                    authenticate(mBiometricAuthentication, listener, restartPredicate, i, key, value);
                } else {
                    listener.onResult(new Goldfinger.Result(Goldfinger.Type.ERROR, FaceAuthResult.AUTH_TIME_OUT, value, mTips.tipInfo, mTips.errorCode, TAG));
                }
            } else {
                listener.onResult(new Goldfinger.Result(Goldfinger.Type.ERROR, FaceAuthResult.ERROR_UNKNOWN, value, mTips.tipInfo, mTips.errorCode, TAG));
            }
        }
    };
}
